#include "Game.h"
#include "../Renderer/ShaderManager.h"

#include "../Input/Input.h"
#include "../Audio/AudioManager.h"
#include "../Utility/Renderer/TextureManager.h"
#include "../Utility/Math/MathHelper.h"
#include "../Renderer/RenderController.h"
#include "../Animation/AnimationManager.h"
#include "../Networking/Client.h"
#include "../Networking/Server.h"


#ifdef _DEBUG
#include "../Utility/Misc/DebugOutput.h"
#include<sstream>
using std::stringstream;
#endif

#include <process.h>

#include <fstream>
using std::ifstream;
using std::ios_base;

bool Game::s_bRunning;
Timer Game::s_Timer;

RenderGObject	Game::renderable;
Camera			Game::camera;
HANDLE			Game::updateThread;
HANDLE			Game::serverThread;
HANDLE			Game::clientThread;
CRITICAL_SECTION Game::cs;
vector<char>	Game::input;

/////////////////////////
// NETWORKING TEST STUFF
int setup;
bool moveForward;
bool moveBackward;
bool moveLeft;
bool moveRight;
////////////////////////

Game::Game()
{
}

bool Game::Initialize(HWND _hWnd, bool _bFullscreen, bool _bVsync, int _nScreenWidth, int _nScreenHeight)
{
	LoadKeyBindings("Res/Data/Controls/DefaultBindings.txt");

	///////////////////////////////
	// TEST SERVER/CLIENT CODE
	moveForward = false;
	moveBackward = false;
	moveLeft = false;
	moveRight = false;
	setup = 0;
	moveForward = false;

	InitializeCriticalSection(&cs);
	
	while(setup != 1 && setup != 2 && setup != 3)
	{
		cout << "Create Listen Server <1> Create Dedicated Server <2> Create Client <3>: ";
		cin >> setup;
	}

	EventSystem::RegisterClient(PLAYER_MOVE_SV, HandleEvent);

	////////////////////////////////

	bool bResult = D3D9Renderer::Initialize(_hWnd, _bFullscreen, _bVsync, _nScreenWidth, _nScreenHeight);
	Input::Init();
	RenderController::Initialize();
	AnimationManager::Initialize();

	/*
	* Move this snippet of code to the video settings screen
	* It opens the adapter.bin and parses the data, which holds the screens supported resolutions
	
	#ifdef _DEBUG
	stringstream adapterDetails;
	adapterDetails << "SUPPORTED RESOLUTIONS: \n";
	#endif

	ifstream adapterIn;
	adapterIn.open("Res/Data/adapter.bin", ios_base::beg | ios_base::binary);
	
	if(adapterIn.good())
		adapterIn.seekg(0, ios_base::end);
	int size = (int)adapterIn.tellg();

	for( int i = 0; i < size/12; ++i)
	{
		if (adapterIn.good())
		{
			unsigned int width, height, refresh;
			adapterIn.seekg(12 * i);
			adapterIn.read((char *)&width, sizeof 4);
			adapterIn.read((char *)&height, sizeof 4);
			adapterIn.read((char *)&refresh, sizeof 4);
			
			#ifdef _DEBUG
			adapterDetails << width << "x";
			adapterDetails << height;
			adapterDetails << " :: Refresh Rate: " << refresh;
			adapterDetails << "\n";
			#endif
		}
	}
	adapterIn.close();

	#ifdef _DEBUG
	DebugOutput::Print(DebugOutput::INFO, adapterDetails.str().c_str());
	#endif

	* End of snippet
	*/

	s_bRunning = bResult;
	s_Timer.Init();
	
	CreateThreads();

	LoadLevel("Res/Level/Test_Level.lvl");
	

	camera.SetPosition(0.0f ,5.0f,-20.0f);

	return bResult;
}

void Game::LoadLevel(const char* const _filePath)
{
	char line[256];
	unsigned int lastObjIndex;
	unsigned int lastDebugObjectIndex;
	unsigned int lastPlayer;

	std::string subPath;
	ifstream in;
	in.open(_filePath, ios_base::binary);
	
	if(in.good())
	{
		while( in.getline(line,256) )
		{
			std::istringstream newLine(line, std::istringstream::in);

			string command;

			newLine >> command;			

			if(command == "robj")
			{
				//Load RenderObject
				newLine >> subPath;
				lastObjIndex = RenderController::LoadObject(subPath.c_str());
			}
			else if(command == "opos")
			{
				//Set position for the RenderObject
				float posX, posY, posZ;
				newLine >> posX >> posY >> posZ;

				RenderController::SetStaticObjectPosition(lastObjIndex, posX, posY, posZ);
			}
			else if(command == "aobj")
			{
				//Load RenderObject
				newLine >> subPath;
				lastPlayer = RenderController::LoadPlayer(subPath.c_str());
			}
			else if(command == "apos")
			{
				//Load RenderObject
				float posX, posY, posZ;
				newLine >> posX >> posY >> posZ;

				RenderController::SetPlayerPosition(lastPlayer, posX, posY, posZ);
			}
			else if(command == "sbox")
			{
				//Set position for the RenderObject
				float scaleX, scaleY, scaleZ;
				newLine >> scaleX >> scaleY >> scaleZ;

				lastDebugObjectIndex = RenderController::LoadDebugGeometry(BOX, scaleX, scaleY, scaleZ);
			}
			else if(command == "spos")
			{
				//Set position for the RenderObject
				float posX, posY, posZ;
				newLine >> posX >> posY >> posZ;

				RenderController::SetDebugGeometryPosition(lastDebugObjectIndex, posX, posY, posZ);
			}
		}
	}
}

void Game::LoadKeyBindings(const char* const _filePath)
{
	ifstream in(_filePath, ios_base::in);

	if(in.is_open())
	{
		char line[256];

		while( in.getline(line,256) )
		{
			istringstream newLine(line, istringstream::in);

			string command;

			newLine >> command;

			int key;

			if(command == "MoveForward")
			{				
				newLine >> key;
				input.push_back(key);
			}
			else if(command == "MoveBackward")
			{
				newLine >> key;
				input.push_back(key);
			}
			else if(command == "StrafeLeft")
			{
				newLine >> key;
				input.push_back(key);
			}
			else if(command == "StrafeRight")
			{
				newLine >> key;
				input.push_back(key);
			}
			else if(command == "Jump")
			{
				newLine >> key;
				input.push_back(key);
			}
		}

		in.close();
	}
}

void Game::CreateThreads()
{
	updateThread = (HANDLE)_beginthreadex(NULL, 0, (unsigned int (_stdcall *)(void *))Game::UpdateThread, (void*)0, 0, NULL);

	if(setup == 1)
	{
		EnterCriticalSection(&cs);
		serverThread = (HANDLE)_beginthreadex(NULL, 0, (unsigned int (_stdcall *)(void *))Game::RunServer, (void*)0, 0, NULL);
		clientThread = (HANDLE)_beginthreadex(NULL, 0, (unsigned int (_stdcall *)(void *))Game::RunClient, (void*)0, 0, NULL);
	}
	else if(setup == 2)
	{
		serverThread = (HANDLE)_beginthreadex(NULL, 0, (unsigned int (_stdcall *)(void *))Game::RunServer, (void*)0, 0, NULL);
	}
	else
	{
		clientThread = (HANDLE)_beginthreadex(NULL, 0, (unsigned int (_stdcall *)(void *))Game::RunClient, (void*)0, 0, NULL);
	}
}

void Game::Run()
{
	Render();
}

unsigned int __stdcall Game::UpdateThread(void* _argList)
{
	while(s_bRunning)
	{
		Update();
	}
	return 1;
}

unsigned int __stdcall Game::RunServer(void* _argList)
{
	Server::Init();
	LeaveCriticalSection(&cs);

	while(s_bRunning && Server::IsRunning())
	{
		Server::Update();
	}

	Server::Shutdown();
	return 1;
}

unsigned int __stdcall Game::RunClient(void* _argList)
{
	if(setup == 1)
	{
		EnterCriticalSection(&cs);
		Client::Init();
		LeaveCriticalSection(&cs);
	}
	else
	{
		Client::Init();
	}

	while(s_bRunning && Client::IsRunning())
	{
		Client::Update();
	}

	Client::Shutdown();
	return 1;
}

void Game::Update()
{
	s_Timer.TimeStep();
	static double timeElapsed = 0.0;
	static float posX = 0.0f;
	static float posZ = -20.0f;
	static POINT mousePos = Input::cursorPosition;

	//INPUT FLAGS
	static bool wKeyDown = false;
	static bool aKeyDown = false;
	static bool sKeyDown = false;
	static bool dKeyDown = false;

	POINT dtMousePos;

	dtMousePos.x = Input::cursorPosition.x - mousePos.x;
	dtMousePos.y = Input::cursorPosition.y - mousePos.y;

	mousePos = Input::cursorPosition;

	if(moveForward)
	{
		camera.Walk(0.01f*s_Timer.GetDeltaTimeFloat());
	}

	if(moveBackward)
	{
		camera.Walk(-0.01f*s_Timer.GetDeltaTimeFloat());
	}

	if(moveLeft)
	{
		camera.Strafe(-0.01f*s_Timer.GetDeltaTimeFloat());
	}

	if(moveRight)
	{
		camera.Strafe(0.01f*s_Timer.GetDeltaTimeFloat());
	}

	if(setup == 3 || setup == 1)
	{
		if(Input::KeyPressed(input[MOVE_FORWARD]))
		{
			PlayerMoveEvent pm(MOVE_FORWARD);
			EventSystem::SendEvent(PLAYER_MOVE_CL, &pm);
		}
		else if(Input::KeyReleased(input[MOVE_FORWARD]))
		{
			PlayerMoveEvent pm(STOP_FORWARD);
			EventSystem::SendEvent(PLAYER_MOVE_CL, &pm);
		}
		if(Input::KeyPressed(input[MOVE_BACKWARD]))
		{
			PlayerMoveEvent pm(MOVE_BACKWARD);
			EventSystem::SendEvent(PLAYER_MOVE_CL, &pm);
		}
		else if(Input::KeyReleased(input[MOVE_BACKWARD]))
		{
			PlayerMoveEvent pm(STOP_BACKWARD);
			EventSystem::SendEvent(PLAYER_MOVE_CL, &pm);
		}
		if(Input::KeyPressed(input[MOVE_RIGHT]))
		{
			PlayerMoveEvent pm(MOVE_RIGHT);
			EventSystem::SendEvent(PLAYER_MOVE_CL, &pm);
		}
		else if(Input::KeyReleased(input[MOVE_RIGHT]))
		{
			PlayerMoveEvent pm(STOP_RIGHT);
			EventSystem::SendEvent(PLAYER_MOVE_CL, &pm);
		}

		if(Input::KeyPressed(input[MOVE_LEFT]))
		{
			PlayerMoveEvent pm(MOVE_LEFT);
			EventSystem::SendEvent(PLAYER_MOVE_CL, &pm);
		}
		else if(Input::KeyReleased(input[MOVE_LEFT]))
		{
			PlayerMoveEvent pm(STOP_LEFT);
			EventSystem::SendEvent(PLAYER_MOVE_CL, &pm);
		}
	}

	Input::Update();

	//MOVING THE CAMERA
	if(wKeyDown)
	{		
		camera.Walk(0.01f*s_Timer.GetDeltaTimeFloat());
	}
	if(sKeyDown)
	{
		camera.Walk(-0.01f*s_Timer.GetDeltaTimeFloat());
	}
	if(dKeyDown)
	{
		camera.Strafe(0.01f*s_Timer.GetDeltaTimeFloat());
	}
	if(aKeyDown)
	{
		camera.Strafe(-0.01f*s_Timer.GetDeltaTimeFloat());
	}

	camera.Pitch(dtMousePos.y*s_Timer.GetDeltaTimeFloat()/2.0f);
	camera.RotateY(dtMousePos.x*s_Timer.GetDeltaTimeFloat()/2.0f);
	

	//camera.SetPosition(posX ,2.0f,posZ);
	camera.SetLens(0.5f*MathHelper::Pi, 1.0f, 1.0f, 1000.0f);
	camera.UpdateViewMatrix();
	XMMATRIX view     = camera.View();
	XMMATRIX proj     = camera.Proj();
	XMMATRIX viewProj = camera.ViewProj();
	XMFLOAT4X4 auxViewProj;
	XMStoreFloat4x4(&auxViewProj, viewProj);

	RenderController::SetViewProjection(auxViewProj);

	/*if(timeElapsed <= 0.000001)
	{
		AudioManager::GetInstance()->PlaySound(0);
	}*/

	//Use s_Timer.GetDeltaTime() to obtain the time elapsed between update ticks (comes in milliseconds)
	timeElapsed += s_Timer.GetDeltaTime();

	/*if(timeElapsed >= 10000.0)
	{
		AudioManager::GetInstance()->StopSound(0);
		timeElapsed = 0.0;
	}*/

	EventSystem::ProcessEvents();
}

void Game::Render()
{
	D3D9Renderer::Clear(0,0, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(255, 0, 255), 1.0f, 0);

	D3D9Renderer::BeginScene();
	//renderable.RenderObject();
	//D3D9Renderer::RenderGame();
	RenderController::Render();
	RenderController::RenderDebugGeometry();
	D3D9Renderer::EndScene();

	D3D9Renderer::Present(0,0,0,0);
}

void Game::HandleEvent(Event* pEvent)
{
	switch(pEvent->GetEventID())
	{
	case PLAYER_MOVE_SV:
		{
			PlayerMoveEvent* pm = (PlayerMoveEvent*)pEvent->GetParam();

#ifdef _DEBUG
	std::ostringstream msg;
	msg << "Direction = " << (unsigned int)pm->direction;
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif

			switch(pm->direction)
			{
			case MOVE_FORWARD:
				{
					moveForward = true;
					moveBackward = false;
				}
				break;

			case MOVE_BACKWARD:
				{
					moveForward = false;
					moveBackward = true;
				}
				break;

			case MOVE_LEFT:
				{
					moveLeft = true;
					moveRight = false;
				}
				break;

			case MOVE_RIGHT:
				{
					moveLeft = false;
					moveRight = true;
				}
				break;

			case STOP_FORWARD:
				{
					moveForward = false;
				}
				break;

			case STOP_BACKWARD:
				{
					moveBackward = false;
				}
				break;

			case STOP_LEFT:
				{
					moveLeft = false;
				}
				break;

			case STOP_RIGHT:
				{
					moveRight = false;
				}
				break;

			}
		}
		break;
	}
}

void Game::Exit()
{
	TextureManager::ClearTextureCache();
	D3D9Renderer::Shutdown();

	//AudioManager::GetInstance()->StopSound(0);
	//AudioManager::GetInstance()->Shutdown();
}